package cn.safekeeper.plugin.redisson.dao;

import cn.safekeeper.common.utils.SafeKeeperUtils;
import cn.safekeeper.core.manager.SafeKeeperTokenRealm;
import cn.safekeeper.core.session.SafeKeeperSession;
import cn.safekeeper.plugin.redis.config.SafeRedisProperties;
import org.redisson.Redisson;
import org.redisson.api.*;
import org.redisson.config.Config;
import org.redisson.config.TransportMode;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * redis持久化实现
 * @author skylark
 */
public class SafeKeeperTokenRealmRedisson implements SafeKeeperTokenRealm {
    /**
     * redis地址模板
     */
    private static final String REDIS_ADDRESS="redis://%s:%s";
    /**
     * redis地址Sentinel模板
     */
    private static final String REDIS_SENTINEL_ADDRESS="redis://%s";
    /**
     * redisson客户端
     */
    private final RedissonClient redissonClient;

    public SafeKeeperTokenRealmRedisson(){
        //默认创建的地址就是127.0.0.1:6379
        Config config = getConfig();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        redissonClient= Redisson.create(config);
    }

    /**
     * 判断是否是window环境
     * @return 是否
     */
    public boolean isWindows() {
        return System.getProperty("os.name").toLowerCase().contains("windows");
    }

    public Config getConfig(){
        Config config=new Config();
        if(isWindows()){
            config.setTransportMode(TransportMode.NIO);
        }else{
            config.setTransportMode(TransportMode.EPOLL);
        }
        return config;
    }

    private static final Integer SINGLE=0;
    private static final Integer SENTINEL=1;
    private static final Integer CLUSTER=2;

    public SafeKeeperTokenRealmRedisson(SafeRedisProperties safeRedisProperties){
        Config config=null;
        if(safeRedisProperties.getType()==SINGLE){
            config=getConfig();
            config.useSingleServer().setAddress(String.format(REDIS_ADDRESS,safeRedisProperties.getHost(),safeRedisProperties.getPort()))
                    .setDatabase(safeRedisProperties.getDatabase());
        }
        else if(safeRedisProperties.getType()==SENTINEL){
            config=getConfig();
            config.useSentinelServers().setMasterName(safeRedisProperties.getSentinel().getMaster());
            List<String> nodes = safeRedisProperties.getSentinel().getNodes();
            for (String node:nodes) {
                config.useSentinelServers().addSentinelAddress(String.format(REDIS_SENTINEL_ADDRESS,node))
                        .setDatabase(safeRedisProperties.getDatabase());
            }
        }
        else if(safeRedisProperties.getType()==CLUSTER){
            config=getConfig();
            List<String> nodes = safeRedisProperties.getCluster().getNodes();
            for (String node:nodes) {
                config.useClusterServers().addNodeAddress(String.format(REDIS_SENTINEL_ADDRESS,node));
            }
        }
        assert config != null;
        redissonClient= Redisson.create(config);
    }

    /**
     * 根据key获取值
     * @param key 键名称
     * @return 值
     */
    @Override
    public String get(String key) {
        RBucket<String> bucket = redissonClient.getBucket(key);
        if(bucket.isExists()){
            return bucket.get();
        }
        return null;
    }

    /**
     * 对key存储值
     * @param key 键名称
     * @param value 值
     * @param timeout 过期时间
     */
    @Override
    public void set(String key, String value, long timeout) {
        RBucket<String> bucket = redissonClient.getBucket(key);
        // 判断是否为永不过期
        if(timeout == SafeKeeperTokenRealm.NEVER_EXPIRE) {
            bucket.set(value);
        }else{
            bucket.set(value,timeout, TimeUnit.SECONDS);
        }
    }

    @Override
    public void update(String key, String value) {
        RBucket<String> bucket = redissonClient.getBucket(key);
        if(bucket.isExists()){
            //修改值，但是过期时间不变
            set(value,value,getTimeout(key));
        }
    }

    @Override
    public void delete(String key) {
        RBucket<String> bucket = redissonClient.getBucket(key);
        bucket.delete();
    }

    /**
     * 获取key的过期时间
     * @param key 指定key
     * @return 返回剩余的过期时间，如果是永久就返回-1，如果不存在key返回-2
     */
    @Override
    public long getTimeout(String key) {
        RBucket<String> bucket = redissonClient.getBucket(key);
        if(bucket.isExists()){
            long remainTimeToLive = bucket.remainTimeToLive();
            if(remainTimeToLive==SafeKeeperTokenRealm.NEVER_EXPIRE){
                return remainTimeToLive;
            }
            return TimeUnit.MILLISECONDS.toSeconds(remainTimeToLive);
        }
        //不存在就返回-2
        return SafeKeeperTokenRealm.NOT_VALUE_EXPIRE;
    }

    /**
     * 修改key的过期时间，秒级过期
     * @param key 指定key
     * @param timeout 过期时间
     */
    @Override
    public void updateTimeout(String key, long timeout) {
        RBucket<String> bucket = redissonClient.getBucket(key);
        if(bucket.isExists()){
            bucket.expire(timeout,TimeUnit.SECONDS);
        }
    }

    @Override
    public Object getObject(String key) {
        return redissonClient.getBucket(key).get();
    }

    @Override
    public void setObject(String key, Object object, long timeout) {
        RBucket<Object> bucket = redissonClient.getBucket(key);
        bucket.set(object,timeout,TimeUnit.SECONDS);
    }

    @Override
    public void updateObject(String key, Object object) {
        RBucket<Object> bucket = redissonClient.getBucket(key);
        bucket.set(object);
    }

    @Override
    public void deleteObject(String key) {
        RBucket<Object> bucket = redissonClient.getBucket(key);
        bucket.delete();
    }

    @Override
    public long getObjectTimeout(String key) {
        RBucket<Object> bucket = redissonClient.getBucket(key);
        if(bucket.isExists()){
            long remainTimeToLive = bucket.remainTimeToLive();
            if(remainTimeToLive==SafeKeeperTokenRealm.NEVER_EXPIRE){
                return remainTimeToLive;
            }
            return TimeUnit.MILLISECONDS.toSeconds(remainTimeToLive);
        }
        //不存在就返回-2
        return SafeKeeperTokenRealm.NOT_VALUE_EXPIRE;
    }

    @Override
    public void updateObjectTimeout(String key, long timeout) {
        RBucket<Object> bucket = redissonClient.getBucket(key);
        if(bucket.isExists()){
            bucket.expire(timeout,TimeUnit.SECONDS);
        }
    }

    @Override
    public SafeKeeperSession getSession(String sessionId) {
        return (SafeKeeperSession) getObject(sessionId);
    }

    @Override
    public void setSession(SafeKeeperSession safeKeeperSession, long timeout) {
        setObject(safeKeeperSession.getId(), safeKeeperSession,timeout);
    }

    @Override
    public void updateSession(SafeKeeperSession safeKeeperSession) {
        updateObject(safeKeeperSession.getId(), safeKeeperSession);
    }

    @Override
    public void deleteSession(String sessionId) {
        deleteObject(sessionId);
    }

    @Override
    public long getSessionTimeout(String sessionId) {
        return getObjectTimeout(sessionId);
    }

    @Override
    public void updateSessionTimeout(String sessionId, long timeout) {
        updateTimeout(sessionId,timeout);
    }

    @Override
    public List<String> searchData(String prefix, String keyword, int start, int size) {
        RKeys keys = redissonClient.getKeys();
        Collection<String> data=keys.findKeysByPattern(prefix + "*" + keyword + "*");
        List<String> list=new ArrayList<>(data);
        return SafeKeeperUtils.searchList(list, start, size);
    }

    private static final LocalCachedMapOptions DEFAULT_MAP_OPTIONS=
            LocalCachedMapOptions.defaults().evictionPolicy(LocalCachedMapOptions.EvictionPolicy.LFU)
                    //缓存容量的大小
                    .cacheSize(2000)
                    // 断线重连的策略有以下几种：
                    // CLEAR - 如果断线一段时间以后则在重新建立连接以后清空本地缓存
                    // LOAD - 在服务端保存一份10分钟的作废日志
                    //        如果10分钟内重新建立连接，则按照作废日志内的记录清空本地缓存的元素
                    //        如果断线时间超过了这个时间，则将清空本地缓存中所有的内容
                    // NONE - 默认值。断线重连时不做处理。
                    .reconnectionStrategy(LocalCachedMapOptions.ReconnectionStrategy.NONE)
                    // 缓存同步策略有以下几种：
                    // INVALIDATE - 默认值。当本地缓存映射的某条元素发生变动时，同时驱逐所有相同本地缓存映射内的该元素
                    // UPDATE - 当本地缓存映射的某条元素发生变动时，同时更新所有相同本地缓存映射内的该元素
                    // NONE - 不做任何同步处理
                    .syncStrategy(LocalCachedMapOptions.SyncStrategy.INVALIDATE)
                    //缓存闲置最长时间1天
                    .timeToLive(24*60*60,TimeUnit.SECONDS);

    private static final String ROLE_PREFIX=":ROLE";
    private static final String PERMISSION_PREFIX=":PERMISSION";
    /**
     * 删除登录维度中的确定id的角色缓存数据
     * @param loginId 登录id
     * @param loginType 登录维度
     * @param type 0：角色 1：权限
     */
    @Override
    public void clearRoleOrPermissionCacheData(Object loginId, String loginType, Integer type) {
        RLocalCachedMap<String,List<String>> localCachedMap = redissonClient.getLocalCachedMap(loginType, DEFAULT_MAP_OPTIONS);
        boolean empty = localCachedMap.isEmpty();
        if(!empty){
            if(type==0){
                localCachedMap.remove(String.valueOf(loginId).concat(ROLE_PREFIX));
            }else if(type==1){
                localCachedMap.remove(String.valueOf(loginId).concat(PERMISSION_PREFIX));
            }

        }
    }

    /**
     * 获取角色数据
     * @param loginId 登录id
     * @param loginType 登录维度
     * @return 角色集合
     */
    @Override
    public List<String> getRolesByLoginTypeAndLoginId(Object loginId, String loginType) {
        RLocalCachedMap<String,List<String>> localCachedMap = redissonClient.getLocalCachedMap(loginType, DEFAULT_MAP_OPTIONS);
        boolean empty = localCachedMap.isEmpty();
        if(!empty){
            return localCachedMap.get(String.valueOf(loginId));
        }else {
            return null;
        }
    }

    /**
     * 存储角色数据
     * @param loginId 登录id
     * @param loginType 登录维度
     * @param roleList 角色数据
     */
    @Override
    public void putRolesData(Object loginId, String loginType, List<String> roleList) {
        RLocalCachedMap<String,List<String>> localCachedMap = redissonClient.getLocalCachedMap(loginType, DEFAULT_MAP_OPTIONS);
        localCachedMap.put(String.valueOf(loginId),roleList);
    }
}
